//Package process is the public API for execution engine of process. It contains a simple execution framework
//that can be used to implement a process engine.
//
//Thre are 3 concepts in the package:
//
//* Context: the container of variables and expressions.
//
//* Flow: the process flow definition, mostly the container of nodes.
//
//* State: the state of the process.
//
//The definition of process flow is driven by states while nodes executes to change states and variables.
package process

import "fmt"

//Variable the variable interface.
type Variable interface {
	GetName() string
	GetValue() interface{}
	SetValue(val interface{}) error
}

//Context the execution context interface.
type Context interface {
	Get(name string) Variable
	Eval(expr string) interface{}
}

//Flow the flow definition interface.
type Flow interface {
	GetName() string
	GetNode(name string) Node
}

//Node the execution node.
type Node interface {
	GetName() string
	Execute(inst Instance) error
}

//predefined errors
var (
	ErrNotRunnable  = fmt.Errorf("not runnable")
	ErrNodeNotFound = fmt.Errorf("node not found")
)

//Instance the execution instance.
type Instance interface {
	GetContext() Context
	GetFlow() Flow
	GetState() State
}

//Step step forward the process once a node.
func Step(inst Instance) error {
	if inst.GetState().Name() != StateNameRunnable {
		return fmt.Errorf("[Flow %s] %s: %w", inst.GetFlow().GetName(), inst.GetState(), ErrNotRunnable)
	}

	currentNodeName := inst.GetState().GetCurrentNode()
	inst.GetState().set(StateNameRunning, currentNodeName)

	currentNode := inst.GetFlow().GetNode(currentNodeName)
	if currentNode == nil {
		return fmt.Errorf("[Flow %s] %s: %w", inst.GetFlow().GetName(), currentNodeName, ErrNodeNotFound)
	}

	if err := currentNode.Execute(inst); err != nil {
		inst.GetState().set(StateNameError, currentNodeName)
	}

	if inst.GetState().Name() == StateNameRunning {
		inst.GetState().set(StateNameCompleted, "")
	}

	return nil
}

//Run a convenient method to execute the flow instance to a non-runnable state.
func Run(inst Instance) error {
	for inst.GetState().Name() == StateNameRunnable {
		err := Step(inst)
		if err != nil {
			return err
		}
	}
	return nil
}

//Continue continue the flow by given node name.
func Continue(inst Instance, fromNodeName string) error {
	if inst.GetFlow().GetNode(fromNodeName) == nil {
		return fmt.Errorf("[Flow %s] %s: %w", inst.GetFlow().GetName(), fromNodeName, ErrNodeNotFound)
	}
	inst.GetState().set(StateNameRunnable, fromNodeName)
	return nil
}

//Complete complete the current execution.
func Complete(inst Instance) {
	inst.GetState().set(StateNameCompleted, "")
}

//Terminate terminate the current execution by next node.
func Terminate(inst Instance) {
	inst.GetState().set(StateNameTerminated, inst.GetState().GetCurrentNode())
}
